from abc import ABC, abstractmethod
from typing import Iterable, List, Optional

from chernc.analyzer.semantic_piece import SemanticPiece, TypeDefinition, TypedefPiece
from chernc.analyzer.utils import MatchData, RustNode, match_exactly, merge_matches
from chernc.slicer.code_slice import CodeSlice


class SourceBasedPiece(ABC):
    @abstractmethod
    def get_slices(self) -> List[CodeSlice]:
        raise NotImplementedError("The get_slices method in the SourceBasedPiece is abstract.")

    @property
    @abstractmethod
    def kind(self) -> str:
        raise NotImplementedError("The kind method in the SourceBasedPiece is abstract.")

    @property
    @abstractmethod
    def text(self) -> str:
        raise NotImplementedError("The text method in the SourceBasedPiece is abstract.")

    @abstractmethod
    def match(self, rust_nodes: Iterable[RustNode]) -> Optional[MatchData]:
        raise NotImplementedError("The match method in the SourceBasedPiece is abstract.")


class TypedefStructurePiece(SourceBasedPiece):
    alias_piece: TypedefPiece

    # None: match failed
    # otherwise will return a piece
    @classmethod
    def extract_from_pieces(cls, pieces: Iterable[SemanticPiece]) -> List["TypedefStructurePiece"]:
        # extract all typedef declarations
        pieces = list(pieces)
        typedef_pieces = [item for item in pieces if isinstance(item, TypedefPiece) and not item.has_body()]
        structure_pieces = [item for item in pieces if isinstance(item, TypeDefinition)]

        # matched = []
        ret = []
        for tp in typedef_pieces:
            for sp in structure_pieces:
                if not sp.full_name or not sp.name:
                    continue
                if tp.match_exact_type_name(sp.full_name) or tp.match_exact_type_name(sp.name):
                    # match success
                    ret.append(TypedefStructurePiece(tp, sp))
                    structure_pieces.remove(sp)
                    break
        return ret

    def __init__(self, alias_piece: TypedefPiece, td_piece: TypeDefinition):
        self.alias_piece = alias_piece
        self.td_piece = td_piece

    def get_slices(self) -> List[CodeSlice]:
        slices = []
        if self.alias_piece.father is not None:
            slices.append(self.alias_piece.father)
        if self.td_piece.father is not None:
            slices.append(self.td_piece.father)
        return slices

    # now, let's match some rust nodes
    def match(self, rust_nodes: Iterable[RustNode]) -> Optional[MatchData]:
        rust_nodes = list(rust_nodes)
        if (m1 := self.alias_piece.match_exactly(rust_nodes)) is not None:
            if (m2 := self.td_piece.match_exactly(rust_nodes)) is not None:
                return merge_matches([m1, m2])
        else:
            return match_exactly(self.alias_piece.name, rust_nodes, self.td_piece.counter_kinds)

    def __str__(self) -> str:
        return f"alias: {self.alias_piece.name}, typedef: {self.td_piece.name}"

    @property
    def kind(self) -> str:
        return "typedef with construction"

    def matching_failure_description(self) -> str:
        alias_text = self.alias_piece.text
        td_text = self.td_piece.text
        return f"the source code contains\n{alias_text}\n and {td_text}, but they have no match in the translation result."

    @property
    def text(self) -> str:
        return "\n".join([self.alias_piece.text, self.td_piece.text])
